/*
* Copyright (C) 2008-2009 J-P Nurmi jpnurmi@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* $Id: sessiontabwidget.cpp 388 2009-05-06 18:52:23Z jpnurmi $
*/

#include "sessiontabwidget.h"
#include "messageview.h"
#include "settingswidget.h"
#include "completionmodel.h"
#include "messageparser.h"
#include "ircmodelitem.h"
#include "ircmodel.h"
#include <ircsession.h>
#include <irc.h>
#include <QtGui>

SessionTabWidget::SessionTabWidget(Irc::Session* session, IrcModel* model, QWidget* parent) :
    QTabWidget(parent), alertTimer(0), model(model), sessio(session)
{
    // workaround QTextEdit::append() autoscrolling:
    // tab icons are too big by default -> the tab bar gets resized when icons appear
    // -> the text edit gets resized and autoscroll is messed up
    int s = style()->pixelMetric(QStyle::PM_TabBarIconSize, 0, this) - 2;
    setIconSize(QSize(s, s));

    settings = new SettingsWidget(this);
    connect(settings, SIGNAL(connectTo(Connection)), this, SIGNAL(connectToImpl(Connection)));
    setCornerWidget(settings);

    connect(this, SIGNAL(currentChanged(int)), this, SLOT(tabActivated(int)));

    QShortcut* shortcut = new QShortcut(QKeySequence::Close, this);
    connect(shortcut, SIGNAL(activated()), this, SLOT(closeCurrentView()));

    shortcut = new QShortcut(QKeySequence(tr("Alt+Left")), this);
    connect(shortcut, SIGNAL(activated()), this, SLOT(moveToPrevTab()));

    shortcut = new QShortcut(QKeySequence(tr("Alt+Right")), this);
    connect(shortcut, SIGNAL(activated()), this, SLOT(moveToNextTab()));

    QPixmap pixmap(iconSize());
    pixmap.fill(Qt::transparent);
    emptyIcon.addPixmap(pixmap);

    sessio->connectSlotsByName(this);

    parser = new MessageParser(this);
    parser->connectSlotsByName(this);
    parser->connectSlotsByName(sessio);
    connect(parser, SIGNAL(cmdHelp(QString)), this, SLOT(logHelp(QString)));
}

SessionTabWidget::~SessionTabWidget()
{
}

Irc::Session* SessionTabWidget::session() const
{
    return this->sessio;
}

uint SessionTabWidget::hashKey() const
{
    Q_ASSERT(sessio);
    return qHash(sessio->host() + sessio->port() + sessio->ident() + sessio->nick() + sessio->password() + sessio->realName());
}

int SessionTabWidget::zoomLevel() const
{
    Q_ASSERT(!views.isEmpty());
    return views.values().value(0)->zoomLevel();
}

void SessionTabWidget::setZoomLevel(int level)
{
    foreach (MessageView* view, views)
        view->setZoomLevel(level);
}

void SessionTabWidget::zoomIn()
{
    MessageView* firstView = qobject_cast<MessageView*>(widget(0));
    if (firstView)
    {
        firstView->zoomIn();
        setZoomLevel(firstView->zoomLevel());
    }
}

void SessionTabWidget::zoomOut()
{
    MessageView* firstView = qobject_cast<MessageView*>(widget(0));
    if (firstView)
    {
        firstView->zoomOut();
        setZoomLevel(firstView->zoomLevel());
    }
}

void SessionTabWidget::save()
{
    QSettings settings;
    settings.beginGroup("State");
    settings.beginGroup(QString::number(hashKey()));
    settings.setValue("tab-index", currentIndex());
}

void SessionTabWidget::restore()
{
    QSettings settings;
    settings.beginGroup("State");
    settings.beginGroup(QString::number(hashKey()));
    setCurrentIndex(settings.value("tab-index", 1).toInt());
}

void SessionTabWidget::on_connected()
{
    model->connected(sessio);
}

void SessionTabWidget::on_disconnected()
{
    model->disconnected(sessio);
}

void SessionTabWidget::on_msgJoined(const QString& origin, const QString& channel)
{
    model->joined(sessio, origin, channel);

    prepareTarget(origin, channel, true, settings->highlightJoins());
    foreach (MessageView* view, views)
    {
        bool originMatches = view->matches(origin);
        bool channelMatches = view->matches(channel);
        if (originMatches || channelMatches)
        {
            if (settings->showJoins())
                view->logMessage(origin, "! %1 joined %2", channel);
        }
    }
}

void SessionTabWidget::on_msgParted(const QString& origin, const QString& channel, const QString& message)
{
    foreach (MessageView* view, views)
    {
        bool originMatches = view->matches(origin);
        bool channelMatches = view->matches(channel);
        if (originMatches || channelMatches)
        {
            if (sessio->nick() == origin)
            {
                views.remove(views.key(view));
                view->deleteLater();
            }
            else if (settings->showParts())
            {
                prepareTarget(origin, view->receiver(), false, settings->highlightParts()); // don't create
                if (!message.isEmpty())
                    view->logMessage(origin, "! %1 parted %2", QString("%1 (%2)").arg(channel).arg(message));
                else
                    view->logMessage(origin, "! %1 parted %2", QString("%1").arg(channel));
            }
        }
    }
    int res = origin.compare(sessio->nick(), Qt::CaseInsensitive);
    model->parted(sessio, origin, channel, message, res == 0);
}

void SessionTabWidget::on_msgQuit(const QString& origin, const QString& message)
{
    foreach (MessageView* view, views)
    {
        bool matches = view->matches(origin);
        bool contains = view->contains(origin);

        if (matches || contains)
        {
            if (settings->showQuits())
            {
                prepareTarget(origin, view->receiver(), false, settings->highlightQuits()); // don't create
                if (!message.isEmpty())
                    view->logMessage(origin, "! %1 has quit (%2)", message);
                else
                    view->logMessage(origin, "! %1 %2", "has quit");
            }
        }
    }
    model->quit(sessio, origin, message);
}

void SessionTabWidget::on_msgNickChanged(const QString& origin, const QString& nick)
{
    foreach (MessageView* view, views)
    {
        bool own = sessio->nick() == nick;
        bool matches = view->matches(origin);
        bool contains = view->contains(origin);
        if (matches || contains || own)
        {
            if (settings->showNicks())
            {
                prepareTarget(origin, view->receiver(), false, settings->highlightNicks()); // don't create
                view->logMessage(origin, "! %1 changed nick to %2", nick);
            }
            if (matches)
            {
                views[nick.toLower()] = views.take(view->receiver().toLower());
                setTabText(indexOf(view), nick);
                view->setReceiver(nick);
            }
        }
    }
    model->nickChanged(sessio, origin, nick);
}

void SessionTabWidget::on_msgModeChanged(const QString& origin, const QString& receiver, const QString& mode, const QString& args)
{
    QString target = prepareTarget(origin, receiver, true, settings->highlightModes());
    if (settings->showModes())
        views[target]->logMessage(origin, "! %1 %2", QString("sets mode %1 %2").arg(mode).arg(args));

    model->channelModeChanged(sessio, origin, receiver, mode, args);
}

void SessionTabWidget::on_msgTopicChanged(const QString& origin, const QString& channel, const QString& topic)
{
    QString target = prepareTarget(origin, channel, true, settings->highlightTopics());
    if (settings->showTopics())
        views[target]->logMessage(origin, "! %1 %2", QString("sets topic \"%1\"").arg(topic));

    model->topicChanged(sessio, origin, channel, topic);
}

void SessionTabWidget::on_msgInvited(const QString& origin, const QString& receiver, const QString& channel)
{
    MessageView* view = static_cast<MessageView*>(currentWidget());
    view->logMessage(origin, "! %1 %2", QString("invited %1 to %2").arg(receiver).arg(channel));

    model->invited(sessio, origin, receiver, channel);
}

void SessionTabWidget::on_msgKicked(const QString& origin, const QString& channel, const QString& nick, const QString& message)
{
    QString target = prepareTarget(origin, channel, true, settings->highlightKicks());
    if (settings->showKicks())
    {
        if (!message.isEmpty())
            views[target]->logMessage(origin, "! %1 %2", QString("kicked %1 (%2)").arg(nick).arg(message));
        else
            views[target]->logMessage(origin, "! %1 %2", QString("kicked %1").arg(nick));
    }

    model->kicked(sessio, origin, channel, nick, message);
}

void SessionTabWidget::on_msgMessageReceived(const QString& origin, const QString& receiver, const QString& message)
{
    QString target = prepareTarget(origin, receiver);
    bool highlight = message.contains(sessio->nick());
    bool privmsg = !target.startsWith('#') && !target.startsWith('&');
    if (highlight || privmsg)
    {
        if (tabText(currentIndex()).toLower() != target)
            alertees += target;
        alert();
    }
    views[target]->receiveMessage(origin, message, highlight);

    model->channelMessageReceived(sessio, origin, receiver, message);
}

void SessionTabWidget::on_msgNoticeReceived(const QString& origin, const QString& receiver, const QString& message)
{
    QString target = prepareTarget(origin, receiver);
    bool highlight = message.contains(sessio->nick());
    if (highlight)
        alert();
    views[target]->receiveNotice(origin, message, highlight);

    model->noticeReceived(sessio, origin, receiver, message);
}

void SessionTabWidget::on_msgCtcpRequestReceived(const QString& origin, const QString& request)
{
    MessageView* view = static_cast<MessageView*>(currentWidget());
    view->logMessage(origin, "! %1 %2", QString("requested CTCP-%1").arg(request.split(QRegExp("\\s")).first().toUpper()));
    if (!request.compare("VERSION", Qt::CaseInsensitive))
        sessio->cmdCtcpReply(origin, QString("%1 %2 %3").arg(request).arg(qApp->applicationName()).arg(qApp->applicationVersion()));
    else if (request.startsWith("PING", Qt::CaseInsensitive))
        sessio->cmdCtcpReply(origin, request);
    else if (request.startsWith("TIME", Qt::CaseInsensitive))
        sessio->cmdCtcpReply(origin, QString("%1 %2").arg(request).arg(QDateTime::currentDateTime().toString()));
    else
        qFatal("SessionTabWidget::on_ctcpRequestReceived '%s' requested '%s'", qPrintable(origin), qPrintable(request));

    model->ctcpRequestReceived(sessio, origin, request);
}

void SessionTabWidget::on_msgCtcpReplyReceived(const QString& origin, const QString& reply)
{
    QString message = reply;
    if (message.startsWith("PING", Qt::CaseInsensitive))
    {
        QStringList params = message.split(QRegExp("\\s"), QString::SkipEmptyParts);
        if (!params.isEmpty())
        {
            QDateTime dateTime = QDateTime::fromTime_t(params.last().toInt());
            if (dateTime.isValid())
            {
                QDateTime current = QDateTime::currentDateTime();
                int msecs = dateTime.time().msecsTo(current.time());
                if (msecs < 1000)
                    message = QString("PING %1ms").arg(msecs);
                else if (msecs < 60000)
                    message = QString("PING %1s").arg(msecs / 1000.0, 0, 'f', 1);
                else
                    message = QString("PING %1m").arg(msecs / 60000);
            }
        }
    }

    MessageView* view = static_cast<MessageView*>(currentWidget());
    view->logMessage(origin, "! %1 %2", QString("replied CTCP-%1").arg(message));

    model->ctcpReplyReceived(sessio, origin, message);
}

void SessionTabWidget::on_msgCtcpActionReceived(const QString& origin, const QString& receiver, const QString& action)
{
    QString target = prepareTarget(origin, receiver);
    bool highlight = action.contains(sessio->nick());
    if (highlight)
        alert();
    views[target]->receiveAction(origin, action, highlight);

    model->ctcpActionReceived(sessio, origin, receiver, action);
}

void SessionTabWidget::on_msgNumericMessageReceived(const QString& origin, uint code, const QStringList& params)
{
    model->numericMessageReceived(sessio, origin, code, params);

    MessageView* currentView = static_cast<MessageView*>(currentWidget());
    if (!currentView)
        prepareTarget(origin, QString(), true, false); // create, don't highlight
    currentView = static_cast<MessageView*>(currentWidget());
    switch (code)
    {
    case Irc::Rfc::RPL_WELCOME:
        {
            MessageView* view = views.take(tabText(0).toLower());
            views[origin.toLower()] = view;
            setTabText(0, origin);
            tabActivated(currentIndex()); // update window title
            emit tabTextChanged(origin);
        }
        break;

    case Irc::Rfc::RPL_WHOISUSER:
        {
            currentView->logMessage(params.value(1), "! %1 is %2", QString("%1@%2 (%3)").arg(params.value(2)).arg(params.value(3)).arg(params.value(5)));
        }
        return;

    case Irc::Rfc::RPL_WHOISSERVER:
        {
            currentView->logMessage(params.value(1), "! %1 is online via %2", QString("%1 (%2)").arg(params.value(2)).arg(params.value(3)));
        }
        return;

    case Irc::Rfc::RPL_WHOISOPERATOR:
        {
            qDebug() << "numeric:" << origin << code << params;
            Q_ASSERT(false);
            //currentView->logMessage(params.value(1), "! %1 is online via %2", QString("%1 (%2)").arg(params.value(2)).arg(params.value(3)));
        }
        return;

    case Irc::Rfc::RPL_WHOWASUSER:
        {
            qDebug() << "numeric:" << origin << code << params;
            Q_ASSERT(false);
            //currentView->logMessage(params.value(1), "! %1 is online via %2", QString("%1 (%2)").arg(params.value(2)).arg(params.value(3)));
        }
        return;

    case Irc::Rfc::RPL_WHOISIDLE:
        {
            QTime idle = QTime().addSecs(params.value(2).toInt());
            currentView->logMessage(params.value(1), "! %1 has been idle for %2", idle.toString());

            QDateTime signon = QDateTime::fromTime_t(params.value(3).toInt());
            currentView->logMessage(params.value(1), "! %1 has been online since %2", signon.toString());
        }
        return;

    case Irc::Rfc::RPL_ENDOFWHOIS:
        {
            // End of /WHOIS list.
        }
        return;

    case Irc::Rfc::RPL_WHOISCHANNELS:
        {
            currentView->logMessage(params.value(1), "! %1 is on channels %2", params.value(2));
        }
        return;

    case 320:
        {
            currentView->logMessage(params.value(1), "! %1 %2", params.value(2));
        }
        return;

    case Irc::Rfc::RPL_CHANNELMODEIS:
        {
            currentView->logMessage(params.value(1), "! %1 mode is %2", params.value(2));
        }
        return;

    case Irc::Rfc::RPL_CHANNELURL:
        {
            currentView->logMessage(params.value(1), "! %1 url is %2", params.value(2));
        }
        return;

    case Irc::Rfc::RPL_CHANNELCREATED:
        {
            QDateTime dateTime = QDateTime::fromTime_t(params.value(2).toInt());
            currentView->logMessage(params.value(1), "! %1 was created %2", dateTime.toString());
        }
        return;

    case Irc::Rfc::RPL_NOTOPIC:
        {
            QString target = prepareTarget(QString(), params.value(1), true, false); // create, don't highlight
            views[target]->logMessage(QString(), "%1! no topic set%2", QString());
        }
        return;

    case Irc::Rfc::RPL_TOPIC:
        {
            QString target = prepareTarget(QString(), params.value(1), true, false); // create, don't highlight
            views[target]->logMessage(QString(), "%1! topic is \"%2\"", params.value(2));
        }
        return;

    case Irc::Rfc::RPL_TOPICSET:
        {
            QDateTime dateTime = QDateTime::fromTime_t(params.value(3).toInt());
            if (dateTime.isValid())
            {
                QString target = prepareTarget(QString(), params.value(1), true, false); // create, don't highlight
                views[target]->logMessage(params.value(2), "! topic set %2 by %1", dateTime.toString());
            }
        }
        return;

    case Irc::Rfc::RPL_INVITING:
        {
            QString target = prepareTarget(QString(), params.value(2), true, false); // create, don't highlight
            views[target]->logMessage(QString(), "%1! inviting %2", QString("%1 to %2").arg(params.value(1)).arg(params.value(2)));
        }
        return;

    case Irc::Rfc::RPL_VERSION:
        {
            currentView->logMessage(origin, "! %1 version is %2", params.value(1));
        }
        return;

    case Irc::Rfc::RPL_NAMREPLY:
        {
            QStringList list = params;
            list.removeAll("=");
            list.removeAll("@");

            QString target = prepareTarget(QString(), list.value(1), true, false); // create, don't highlight
            QStringList nicks = list.value(2).split(" ", QString::SkipEmptyParts);
            QString msg = QString("[ %2 ]").arg(nicks.join(" ] [ "));
            views[target]->logMessage(QString(), "%1! %2", msg);
            //tabActivated(currentIndex());
        }
        return;

    case Irc::Rfc::RPL_ENDOFNAMES:
        return;

    case Irc::Rfc::RPL_TIME:
        {
            currentView->logMessage(params.value(1), "! %1 time is %2", params.value(2));
        }
        return;

    default:
        break;
    }

    //qDebug() << "numeric:" << origin << code;
    currentView->logMessage(QString(), "%1%2", QString("[%1] %2").arg(code).arg(params.join(" ")));
}

void SessionTabWidget::on_msgUnknownMessageReceived(const QString& origin, const QStringList& params)
{
    // TODO
    qWarning() << "unknown:" << origin << params;

    model->unknownMessageReceived(sessio, origin, params);
}

void SessionTabWidget::clearView(const QString& view)
{
    MessageView* messageView = 0;
    if (view.isNull())
        messageView = static_cast<MessageView*>(currentWidget());
    else
        messageView = views.value(view.toLower());
    if (messageView)
        messageView->clear();
}

void SessionTabWidget::closeCurrentView(const QString& message)
{
    if (count() > 1)
    {
        int index = currentIndex();
        QString receiver = tabText(index);
        closeView(receiver, message);
    }
}

void SessionTabWidget::openView(const QString& receiver)
{
    QString target = receiver.toLower();
    if (views.contains(target))
    {
        setCurrentWidget(views.value(target));
    }
    else
    {
        MessageView* view = new MessageView(receiver, this);
        connect(view, SIGNAL(send(QString)), this, SLOT(send(QString)));
        connect(view, SIGNAL(query(QString)), this, SLOT(openView(QString)));
        connect(view, SIGNAL(command(QString)), sessio, SLOT(sendRaw(QString)));

        views.insert(target, view);
        addTab(view, receiver);
        setCurrentIndex(count() - 1);

        QSettings settings;
        settings.beginGroup("State");
        int zoomLevel = settings.value("zoom", -1).toInt();
        if (zoomLevel != -1)
            view->setZoomLevel(zoomLevel);

        CompletionModel* proxyModel = new CompletionModel(view);

        IrcModelItem* sessionItem = model->addSession(sessio, receiver);
        if (sessionItem->name() == receiver)
        {
            proxyModel->setCompletionIndex(sessionItem->index());
        }
        else
        {
            IrcModelItem* receiverItem = sessionItem->childItem(receiver);
            proxyModel->setCompletionIndex(receiverItem->index());
        }
        view->setModel(proxyModel);
    }
}

void SessionTabWidget::closeView(const QString& receiver, const QString& message)
{
    QString tmp = receiver;
    if (!tmp.startsWith('#') && !tmp.startsWith('&') && !views.contains(tmp.toLower()))
        tmp.prepend("#");

    if (tmp.startsWith('#') || tmp.startsWith('&'))
        sessio->cmdPart(tmp, message);

    MessageView* view = views.take(tmp.toLower());
    if (view)
    {
        if (indexOf(view) == 0)
        {
            // closing a server tab
            emit disconnectFrom(QString());
            deleteLater();
        }
        view->deleteLater();
    }
}

void SessionTabWidget::tabActivated(int index)
{
    if (index == -1)
        index = currentIndex();
    
    setTabIcon(index, QIcon());
    updateTabIcon();

    QString receiver = tabText(index);
    alertees.removeAll(receiver.toLower());
    window()->setWindowFilePath(receiver);

    if (index != -1)
        widget(index)->setFocus();
}

void SessionTabWidget::moveToNextTab()
{
    int index = currentIndex();
    if (++index >= count())
        index = 0;
    setCurrentIndex(index);
}

void SessionTabWidget::moveToPrevTab()
{
    int index = currentIndex();
    if (--index < 0)
        index = count() - 1;
    setCurrentIndex(index);
}

void SessionTabWidget::send(const QString& message)
{
    if (message.trimmed().isEmpty())
        return;

    QString defaultReceiver = tabText(currentIndex());
    parser->setReceivers(views.keys());
    parser->parse(message, defaultReceiver);
}

void SessionTabWidget::alert()
{
    if (!alertTimer)
    {
        alertTimer = new QTimer(this);
        connect(alertTimer, SIGNAL(timeout()), this, SLOT(alert()));
        alertTimer->start(500);
        emit alertStarted();
    }

    bool emitted = false;
    for (int i = 0; i < count(); ++i)
    {
        if (alertees.contains(tabText(i).toLower()))
        {
            QIcon icon = tabIcon(i);
            if (icon.cacheKey() == emptyIcon.cacheKey())
                icon = QApplication::windowIcon();
            else
                icon = emptyIcon;
            setTabIcon(i, icon);

            if (!emitted)
            {
                emit tabIconChanged(icon);
                emitted = true;
            }
        }
    }

    if (alertees.isEmpty())
    {
        alertTimer->stop();
        alertTimer->deleteLater();
        alertTimer = 0;
        updateTabIcon();
    }
}

void SessionTabWidget::logMessage(const QString& receiver, const QString& message)
{
    on_msgMessageReceived(sessio->nick(), receiver, message);
}

void SessionTabWidget::logNotice(const QString& receiver, const QString& message)
{
    on_msgNoticeReceived(sessio->nick(), receiver, message);
}

void SessionTabWidget::logCtcpAction(const QString& receiver, const QString& message)
{
    on_msgCtcpActionReceived(sessio->nick(), receiver, message);
}

void SessionTabWidget::logHelp(const QString& message)
{
    MessageView* view = static_cast<MessageView*>(currentWidget());
    view->receiveHelp(message);
}

void SessionTabWidget::updateTabIcon()
{
    bool allNull = true;
    for (int i = 0; i < count(); ++i)
    {
        if (!tabIcon(i).isNull())
        {
            allNull = false;
            break;
        }
    }
    if (allNull)
        emit tabIconChanged(QIcon());
    else
        emit tabIconChanged(QApplication::windowIcon());
}

QString SessionTabWidget::prepareTarget(const QString& sender, const QString& receiver, bool create, bool highlight)
{
    QString target = receiver;

    if (target.contains("*") || target.contains("?"))
        target = sessio->nick();

    if (target == sessio->nick())
    {
        if (target == sender)
            target = sessio->host();
        else
            target = sender;
    }

    if (target.isEmpty() || target == "AUTH")
    {
        if (count() > 0)
            target = tabText(0);
        else
            target = sessio->host();
    }

    if (create && !views.contains(target.toLower()))
        openView(target);

    int index = indexOf(views.value(target.toLower()));
    if (highlight && (index != currentIndex() || !isVisible()))
    {
        setTabIcon(index, QApplication::windowIcon());
        emit tabIconChanged(QApplication::windowIcon());
    }

    return target.toLower();
}
